﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows;
using System.Xml.Linq;
using CharacterModel.Extensions;
using Framework.Attributes;

namespace CharacterModel
{
    [RepositoryCache(true)]
    public class Feature : IIdName
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public FeatureType FeatureType { get; set; }
        public double BoundsLeft { get; set; }
        public double BoundsTop { get; set; }
        public double BoundsWidth { get; set; }
        public double BoundsHeight { get; set; }

        private Rect? _bounds;
        [Exclude]
        public Rect Bounds
        {
            get
            {
                if (_bounds == null)
                    _bounds = new Rect(BoundsLeft, BoundsTop, BoundsWidth, BoundsHeight);
                return _bounds.Value;
            }
        }

        public void CalculateBounds()
        {
            var rect = _layers
                .SelectMany(layer => layer.Paths)
                .Select(path => path.SvgPath)
                .SelectMany(path => Regex
                                        .Split(path, "[^-.,0-9]")
                                        .Where(s => !string.IsNullOrEmpty(s))
                                        .Select(s => s.Split(','))
                                        .Where(s => s.Length == 2)
                                        .Select(ar => new Point(Convert.ToDouble(ar[0]), Convert.ToDouble(ar[1]))))
                .BoundingRectangle();

            BoundsLeft = rect.Left;
            BoundsTop = rect.Top;
            BoundsWidth = rect.Width;
            BoundsHeight = rect.Height;
        }

        private List<Layer> _layers = new List<Layer>();

        [Exclude]
        public IEnumerable<Layer> DynamicLayers
        {
            get
            {
                var layerBuilders = new DynamicLayerBuilder[]
                                        {
                                            new EyeLayerBuilder(),
                                            new HeadLayerBuilder(),
                                            new NoseLayerBuilder(),
                                            new MouthLayerBuilder()
                                        };
                return layerBuilders.SelectMany(layerBuilder => layerBuilder.GetDynamicLayers(this));
            }
        }

        public ICollection<Layer> Layers
        {
            get { return _layers; }
            set
            {
                _layers = value.ToList();
                CalculateBounds();
            }
        }

        private List<MarkerPoint> _markerPoints = new List<MarkerPoint>();
        public ICollection<MarkerPoint> MarkerPoints
        {
            get { return _markerPoints; }
            set { _markerPoints = value.ToList(); }
        }

        private static Dictionary<FeatureTypeEnum, List<MarkerPoint>> GetMarkerDictionary(IEnumerable<XElement> markerElements)
        {
            var dictionary = new Dictionary<FeatureTypeEnum, List<MarkerPoint>>();
            dictionary[FeatureTypeEnum.Head] = new List<MarkerPoint>();
            dictionary[FeatureTypeEnum.Eyes] = new List<MarkerPoint>();
            dictionary[FeatureTypeEnum.Mouth] = new List<MarkerPoint>();
            dictionary[FeatureTypeEnum.Nose] = new List<MarkerPoint>();
            AddToMarkerDictionary(markerElements, dictionary, "eye_boundary", FeatureTypeEnum.Eyes, AddEyeMarkerPoints);
            AddToMarkerDictionary(markerElements, dictionary, "mouth_boundary", FeatureTypeEnum.Mouth, AddMouthMarkerPoints);
            AddToMarkerDictionary(markerElements, dictionary, "nose_boundary", FeatureTypeEnum.Nose, AddNoseMarkerPoints);
            return dictionary;
        }

        private static void AddToMarkerDictionary(IEnumerable<XElement> markerElements, Dictionary<FeatureTypeEnum, List<MarkerPoint>> dictionary, string markerName, FeatureTypeEnum additionalFeatureType, Action<ICollection<MarkerPoint>, double, double, double, double> markerFunc)
        {
            var boundaryElement = markerElements.FirstOrDefault(elem => elem.Attributes().Single(attr => attr.Name.LocalName == "label").Value == markerName);
            if (boundaryElement != null)
            {
                var rectangleElement = boundaryElement.Elements().FirstOrDefault(elem => elem.Name.LocalName == "rect");
                if (rectangleElement != null)
                {
                    var x = Convert.ToDouble(rectangleElement.Attribute("x").Value);
                    var y = Convert.ToDouble(rectangleElement.Attribute("y").Value);
                    var height = Convert.ToDouble(rectangleElement.Attribute("height").Value);
                    var width = Convert.ToDouble(rectangleElement.Attribute("width").Value);

                    markerFunc(dictionary[additionalFeatureType], x, y, width, height);
                    markerFunc(dictionary[FeatureTypeEnum.Head], x, y, width, height);
                }
            }
        }

        private static void AddMouthMarkerPoints(ICollection<MarkerPoint> markerPoints, double x, double y, double width, double height)
        {
            AddMarkerPoint(markerPoints, x, y, MarkerPointTypeEnum.MouthBoundary3);
            AddMarkerPoint(markerPoints, x + width, y, MarkerPointTypeEnum.MouthBoundary4);
            AddMarkerPoint(markerPoints, x + width / 2, y, MarkerPointTypeEnum.MouthBoundary2);
            AddMarkerPoint(markerPoints, x + width / 2, y + height, MarkerPointTypeEnum.MouthBoundary1);
        }

        private static void AddNoseMarkerPoints(ICollection<MarkerPoint> markerPoints, double x, double y, double width, double height)
        {
            AddMarkerPoint(markerPoints, x, y, MarkerPointTypeEnum.NoseBoundary3);
            AddMarkerPoint(markerPoints, x + width, y, MarkerPointTypeEnum.NoseBoundary4);
            AddMarkerPoint(markerPoints, x, y + height, MarkerPointTypeEnum.NoseBoundary2);
            AddMarkerPoint(markerPoints, x + width, y + height, MarkerPointTypeEnum.NoseBoundary1);
        }

        private static void AddEyeMarkerPoints(ICollection<MarkerPoint> markerPoints, double x, double y, double width, double height)
        {
            AddMarkerPoint(markerPoints, x, y, MarkerPointTypeEnum.EyeBoundary1);
            AddMarkerPoint(markerPoints, x + width, y, MarkerPointTypeEnum.EyeBoundary3);
            AddMarkerPoint(markerPoints, x, y + height, MarkerPointTypeEnum.EyeBoundary2);
            AddMarkerPoint(markerPoints, x + width, y + height, MarkerPointTypeEnum.EyeBoundary4);
        }

        private static void AddMarkerPoint(ICollection<MarkerPoint> markerPoints, double x, double y, MarkerPointTypeEnum markerPointTypeEnum)
        {
            markerPoints.Add(new MarkerPoint
                                 {
                                     MarkerPointType = new MarkerPointType { MarkerPointTypeEnum = markerPointTypeEnum },
                                     PointX = x,
                                     PointY = y
                                 });
        }

        public static IEnumerable<Feature> FromSvg(XDocument document, string featureName)
        {
            var boundaryElements = document
                .Descendants()
                .Where(element => element.Name.LocalName == "g"
                                  && element.Attributes().Any(attr => attr.Name.LocalName == "label")
                                  && element.Attributes().Single(attr => attr.Name.LocalName == "label").Value.EndsWith("_boundary"));

            var markerDictionary = GetMarkerDictionary(boundaryElements);

            return document
                .Descendants()
                .Where(element => element.Name.LocalName == "g")
                .Select(SvgToFeatureConverter.LayerFromGroup)
                .Where(layer => layer != null)
                .GroupBy(layer => FeatureType.FeatureTypeFromLayerType(layer.LayerType.LayerTypeEnum))
                .Where(g => g.Key != FeatureTypeEnum.Head ||
                            g.Count(layer => layer.LayerType.LayerTypeEnum != LayerTypeEnum.FaceColor) > 0)
                .Select(g => new Feature
                                 {
                                     Layers = g.ToList(),
                                     Name = featureName,
                                     FeatureType = new FeatureType {FeatureTypeEnum = g.Key},
                                     MarkerPoints = markerDictionary.ContainsKey(g.Key)
                                                        ? markerDictionary[g.Key]
                                                        : new List<MarkerPoint>()
                                 })
                .ToList();
        }
    }
}